/*
 * Students:
 * Massimiliano Rango - 610826
 * Martina Benedetto - 610962
 * 
 * A.A 2011/2012
 * 
 */  

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.rmi.Naming;
import java.text.ParseException;
import java.util.StringTokenizer;


/**
 *
 * This class represents the Client of the projected application. Once the Client
 * is created, the simulation can begin.
 * 
 * @version 1.0 27 Dec 2011
 * @author Martina Benedetto 
 *
 */

public class Client extends Thread {
	
	private final static int WWW_PORT = 80;
	private final static String HTTP_VER = "HTTP/1.0";
	private final static String PATH_DELIM = "/";
	
	private Configuration m_configuration;
	private String m_RMIAddress;
	private ResultsInterface m_results;
	private GUIInterface m_gui;
	private boolean m_clickStop;
	
	public Client(String HTTPAddress, String RMIAddress, GUIInterface gui) 
			throws Exception {
		String host;
		String path;
		
		URL HTTP = new URL(HTTPAddress);
		URLConnection connHTTP = HTTP.openConnection();
		connHTTP.connect();
	    
		StringTokenizer st = new StringTokenizer(HTTPAddress.substring(7),
				PATH_DELIM);
		host = st.nextToken();
		path = "";
	    	while (st.hasMoreTokens()) {
		    path += PATH_DELIM + st.nextToken();
	    	}
	    	if (!path.endsWith(".txt")) {
		    throw new Exception("Invalid configuration file.");
	    	}
		
	    	URL RMI = new URL(RMIAddress);
	    	URLConnection connRMI = RMI.openConnection();
	    	m_RMIAddress = RMIAddress.substring(7);
		
	    	m_configuration = getConfigurationFile(host, path);
	    	m_results = (ResultsInterface) Naming.lookup("rmi://" 
	    			+ m_RMIAddress + "/Results");
	    	m_gui = gui;
	    	m_clickStop = false;
	}
	
	/**
	 * 
	 * @param in is the stream from which we have to read a value
	 * @param name is the name of the parameter
	 * @return the value of the parameter with name "name"
	 * @throws IOException
	 * @throws ParseException
	 */
	private String readValue(BufferedReader in, String name) 
			throws IOException, ParseException {
		String line = in.readLine().trim(); 			
		while (line != null && (line.startsWith("%") || line.isEmpty())) {
			line = in.readLine().trim();
		}
		if (line != null) {
			StringTokenizer strtok = new StringTokenizer(line, "=");
			if (strtok.countTokens() == 2) {
				String optStr = strtok.nextToken().trim();
				if (optStr.equals(name)) {
					return strtok.nextToken().trim();
				}
			}
		}
		throw new ParseException("Wrong format of the configuration" 
				+ "file. Error found on " + name + " variable.",
				0);
	}
	
	/**
	 * 
	 * @param in in is the stream from which we have to read the values
	 * @return Configuration object which contains the parameters of the
	 * simulation 
	 * @throws IOException
	 * @throws ParseException
	 */
	private Configuration parseConfigurationFile(BufferedReader in) 
			throws IOException, ParseException {        
                Configuration.Protocol protocol = null;
                int nSim = -1;
                int nNodes = -1;
                int g = -1;
                int totEnergy = -1;
                int recEnergy = -1;
                int sendEnergy = -1;
                int sigEnergy = -1;
            	float radius = -1;
            	float prob = -1;
                
                boolean headerEnd = false;
                String line = in.readLine();
                
                while (!headerEnd) {
                	if (line.equals("Connection: close")) {
                		headerEnd = true;
                	}
                	line = in.readLine();
                }
	    
                String proto = readValue(in, "PROTO");
                if (proto.equals("LSM")) {
                	protocol = Configuration.Protocol.LSM;
                } else if (proto.equals("RED")) {
                	protocol = Configuration.Protocol.RED;
                } else {
                	throw new ParseException("Wrong format of the configuration"
	    			+ "file. Error found on PROTO variable.", 0);
                }
	    
                nSim = Integer.parseInt(readValue(in, "NSIM"));
                prob = Float.parseFloat(readValue(in, "p"));
                g = Integer.parseInt(readValue(in, "g"));
                nNodes = Integer.parseInt(readValue(in, "n"));
                radius = Float.parseFloat(readValue(in, "r"));
                totEnergy = Integer.parseInt(readValue(in, "E"));
                sendEnergy = Integer.parseInt(readValue(in, "E_send"));
                recEnergy = Integer.parseInt(readValue(in, "E_receive"));
                sigEnergy = Integer.parseInt(readValue(in, "E_signature"));
                
                return new Configuration(protocol, nSim, nNodes, radius, prob, 
			g, totEnergy, recEnergy, sendEnergy, sigEnergy);
	}
	
	/**
	 * 
	 * @param host is the address of the host
	 * @param path is the path of the file we want to read
	 * @return Configuration object which contains the parameters of the
	 * simulation
	 * @throws IOException
	 * @throws ParseException
	 */
	private Configuration getConfigurationFile(String host, String path) 
			throws IOException, ParseException {
		Socket socket = new Socket(host, WWW_PORT);
		BufferedReader in = new BufferedReader(new InputStreamReader
				(socket.getInputStream()));
		BufferedWriter out = new BufferedWriter(new OutputStreamWriter
				(socket.getOutputStream())); 

		String getRequest = "GET " + path + " " + HTTP_VER + "\r\n\r\n";
		out.write(getRequest);
		out.flush();
		Configuration c = parseConfigurationFile(in);

		in.close();
		out.close();
		
		return c;
	}
	
	public Configuration getConf() {
		return m_configuration;
	}
	
	public void setClickStop() {
		m_clickStop = true;
	}
	
	public void run() {
		int nClones = 0;
		
		m_gui.printInfoArea(m_configuration.toString() + "\n");
		for(int i = 0; i < m_configuration.getNSim() && !m_clickStop;
				i++) {
			m_gui.printInfoArea("Starting simulation " + (i+1) + "/" 
				+ m_configuration.getNSim() + "\n");
			try {
				Configuration confCopy = new Configuration
						(m_configuration);
				Hypervisor h = new Hypervisor(confCopy);
				synchronized(h) {
					h.start();
					while(!h.isCompleted()) {
						h.wait();
					}
					
					m_gui.printRMIArea("Simulation " + (i + 1) + "\n" + confCopy + "\nMin sent messages = " + h.getminSM() + "\nMax sent messages = " 
							+ h.getmaxSM() + "\nAverage sent messages = " + h.getavSM() + "\nStandard deviation sent messages = " + h.getsdSM() 
							+ "\nMin received messages = " + h.getminRM() + "\nMax received messages = " + h.getmaxRM() 
							+ "\nAverage received messages = " + h.getavRM() + "\nStandard deviation received messages = " + h.getsdRM() 
							+ "\nMin signatures = " + h.getminSIG() + "\nMax signatures = " + h.getmaxSIG() + "\nAverage signatures = " 
							+ h.getavSIG() + "\nStandard deviation signatures = " + h.getsdSIG() + "\nMin spent energy = " + h.getminE() 
							+ "\nMax spent energy = " + h.getmaxE() + "\nAverage spent energy = " + h.getavE() 
							+ "\nStandard deviation spent energy = " + h.getsdE() + "\nMin messages in memory = " + h.getminMA() 
							+ "\nMax messages in memory = " + h.getmaxMA() + "\nAverage messages in memory = " + h.getavMA() 
							+ "\nStandard deviation messages in memory = " + h.getsdMA() + "\n\n");
					
					m_results.addResult(new SimResult(h.getConf(), 
						h.getminSM(), h.getmaxSM(), 
						h.getavSM(), h.getsdSM(),
						h.getminRM(), h.getmaxRM(),
						h.getavRM(), h.getsdRM(), 
						h.getminSIG(), h.getmaxSIG(),
						h.getavSIG(), h.getsdSIG(), 
						h.getminE(), h.getmaxE(), 
						h.getavE(), h.getsdE(), 
						h.getminMA(), h.getmaxMA(), 
						h.getavMA(), h.getsdMA()));
					m_gui.printInfoArea("Simulation " + (i + 1) + "/" 
						+ m_configuration.getNSim() 
						+ " completed. ");
					if (confCopy.getCloneFound()) {
						m_gui.printInfoArea(" Clone Found.\n\n");
						nClones++;
					} else if (confCopy.getCirculatingMess() == 0) {
						m_gui.printInfoArea(" No circulating message.\n\n");
					} else if (confCopy.getKilledNodes() == m_configuration.getNNodes() + 1) {
						m_gui.printInfoArea(" No nodes alive.\n\n");
					}
				}
			} catch (Exception e) {
				m_gui.printInfoArea(e.toString());
				return;
			}
		}
		m_gui.printInfoArea("Clones found: " + nClones);
		m_gui.clientFinished();
	}
}
